﻿
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace Extend.Framework.Predicate
{
    public class DynamicMethod<T> : IMemberAccessor
    {
        internal static Func<object, string, object> GetValueDelegate;

        static DynamicMethod()
        {
            GetValueDelegate = GenerateGetValue();
        }

        /// <summary>
        ///     Get the member value of an object.
        /// </summary>
        /// <param name="instance">The object to get the member value from.</param>
        /// <param name="memberName">The member name, could be the name of a property of field. Must be public member.</param>
        /// <returns>The member value</returns>
        public object GetValue(object instance, string memberName)
        {
            return GetValueDelegate(instance, memberName);
        }

        /// <summary>
        ///     Set the member value of an object.
        /// </summary>
        /// <param name="instance">The object to get the member value from.</param>
        /// <param name="memberName">The member name, could be the name of a property of field. Must be public member.</param>
        /// <param name="newValue">The new value of the property for the object instance.</param>
        public void SetValue(object instance, string memberName, object newValue)
        {
            throw new NotImplementedException();
        }

        private static Func<object, string, object> GenerateGetValue()
        {
            Type type = typeof (T);
            ParameterExpression instance = Expression.Parameter(typeof (object), "instance");
            ParameterExpression memberName = Expression.Parameter(typeof (string), "memberName");
            ParameterExpression nameHash = Expression.Variable(typeof (int), "nameHash");
            BinaryExpression calHash = Expression.Assign(nameHash,
                Expression.Call(memberName, typeof (object).GetMethod("GetHashCode")));
            var cases = new List<SwitchCase>();
            foreach (PropertyInfo propertyInfo in type.GetProperties())
            {
                MemberExpression property = Expression.Property(Expression.Convert(instance, typeof (T)),
                    propertyInfo.Name);
                ConstantExpression propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof (int));

                cases.Add(Expression.SwitchCase(Expression.Convert(property, typeof (object)), propertyHash));
            }
            SwitchExpression switchEx = Expression.Switch(nameHash, Expression.Constant(null), cases.ToArray());
            BlockExpression methodBody = Expression.Block(typeof (object), new[] {nameHash}, calHash, switchEx);

            return Expression.Lambda<Func<object, string, object>>(methodBody, instance, memberName).Compile();
        }
    }
}